#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@Author          : ricsy
@File            : conf.py
@Create Time     : 2025/6/2 19:11
@Last Modified   :
@Description     : Sphinx 文档配置文件
"""
import asyncio
import json
import os
import sys
import warnings

from docutils.parsers.rst import directives

from config import custom_extensions, favicons, icons
from sphinx.application import Sphinx
from src.jinja_filters import JinjaFilters
from src.settings import logger, project_root
from src.theme_config_manager import ThemeConfigManager
from theme_customizer.assets import AssetManager
from theme_customizer.code_block import CodeBlockManager
from theme_customizer.context import ContextManager
from theme_customizer.core import CoreManager
from theme_customizer.prod_config import ProductionConfigurator
from theme_customizer.seo import SEOManager
from theme_customizer.translate import TranslatorManager
from utils.config_processor import ConfigProcessor
from utils.time_utils import TimeUtils


# 添加模块路径
sys.path.insert(0, os.path.abspath("../"))  # 将项目根目录加入 Python 路径
sys.path.insert(0, os.path.abspath("../src"))

warnings.filterwarnings("ignore", category=UserWarning, module="jieba._compat")

if sys.platform == "win32":
    # 解决 zmq库（ZeroMQ）与 Windows 默认的 ProactorEventLoop 不兼容问题
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())


source_dir = project_root / "sphinx"  # 指定文档源文件（.rst/.md）的根目录位置
output_dir = project_root / "docs"  # 指定构建输出目录位置
master_doc = "index"  # 指定项目的主文档/入口文件，使用文件名（不带扩展名）
if not os.path.exists(source_dir):
    raise RuntimeError(f"文档目录不存在: {source_dir}. 当前工作目录: {os.getcwd()}")

os.environ["TZ"] = "Asia/Shanghai"
config = ConfigProcessor()

# 动态加载扩展
#  ╔═══════════════════════════════════════════════════════════════════════════╗
#                                   项目信息
#  ╚═══════════════════════════════════════════════════════════════════════════╝
default_ns = config.as_namespace("default")

project = default_ns.project
author = default_ns.author
release = default_ns.release

# noinspection PyShadowingBuiltins
copyright = default_ns.copyright
exclude_patterns = default_ns.exclude_patterns
source_suffix = json.loads(default_ns.source_suffix)

# 语言设置
language = default_ns.language
gettext_compact = default_ns.gettext_compact
# 使用绝对路径
locale_dirs = [str((source_dir / item).as_posix()) for item in default_ns.locale_dirs]
gettext_location = default_ns.gettext_location
gettext_domain = default_ns.gettext_domain
gettext_allowed_domains = default_ns.gettext_allowed_domains
gettext_additional_targets = default_ns.gettext_additional_targets

suppress_warnings = default_ns.suppress_warnings
# ---------------------------------- sphinx_favicon ---------------------------------
# 添加 favicon 元标签
favicons = favicons.FAVICONS

#  ╔═══════════════════════════════════════════════════════════════════════════╗
#                                    项目特性
#  ╚═══════════════════════════════════════════════════════════════════════════╝
features_ns = config.as_namespace("features")

rst_prolog = features_ns.rst_prolog
rst_epilog = features_ns.rst_epilog
substitutions = features_ns.substitutions
highlight_options = features_ns.highlight_options

#  ╔═══════════════════════════════════════════════════════════════════════════╗
#                                   主题信息
#  ╚═══════════════════════════════════════════════════════════════════════════╝
html_ns = config.as_namespace("html")
html_config_ns = html_ns.config
resources_ns = html_config_ns["resources"]

html_theme = html_ns.html_theme
html_theme_options = html_ns.theme[html_theme]
# ================================ 删除与主题无关的配置 ================================
if "ext" in html_theme_options:
    del html_theme_options["ext"]
if "html_sidebars" in html_theme_options:
    html_sidebars = html_theme_options["html_sidebars"]["default"]
    del html_theme_options["html_sidebars"]

html_title = html_ns.html_title
html_logo = html_ns.html_logo
html_favicon = html_ns.html_favicon
html_show_sphinx = html_ns.html_show_sphinx
html_show_copyright = html_ns.html_show_copyright
html_show_sourcelink = html_ns.html_show_sourcelink
html_copy_source = html_ns.html_copy_source
html_baseurl = html_ns.html_baseurl

html_context = html_config_ns["html_context"]

# 添加主题模板目录
templates_path = resources_ns["templates_path"]
templates_path.append(f"_templates/{html_theme}")

html_static_path = resources_ns["html_static_path"]
html_css_files = resources_ns["html_css_files"]
html_js_files = resources_ns["html_js_files"]

#  ╔═══════════════════════════════════════════════════════════════════════════╗
#                                      插件配置
#  ╚═══════════════════════════════════════════════════════════════════════════╝
plugins_ns = config.as_namespace("plugins")
plugins_config_ns = plugins_ns.config

extensions = plugins_ns.default
extend_extensions = {
    ext
    for plugin in plugins_ns.list.values()
    if plugin.get("enabled", False)
    for ext in (
        *plugin.get("extensions", []),
        *plugin.get("extensions_dependencies", []),  # 合并两个扩展列表
    )
}

extensions.extend(extend_extensions)
# 加载自定义扩展
extensions.extend(custom_extensions.extensions)

extensions = list(set(extensions))

# ---------------------------------- autodoc ---------------------------------
autodoc_default_options = plugins_config_ns["autodoc_default_options"]
autodoc_warningiserror = plugins_config_ns["autodoc_warningiserror"]
autodoc_member_order = plugins_config_ns["autodoc_member_order"]
autodoc_typehints = plugins_config_ns["autodoc_typehints"]
autodoc_typehints_format = plugins_config_ns["autodoc_typehints_format"]
autodoc_typehints_description_target = plugins_config_ns[
    "autodoc_typehints_description_target"
]
autodoc_class_signature = plugins_config_ns["autodoc_class_signature"]

# ---------------------------------- copybutton_prompt ---------------------------------
copybutton_prompt_is_regexp = plugins_config_ns["copybutton_prompt_is_regexp"]
copybutton_prompt_text = rf'{plugins_config_ns["copybutton_prompt_text"]}'
copybutton_only_copy_prompt_lines = plugins_config_ns[
    "copybutton_only_copy_prompt_lines"
]
copybutton_remove_prompts = plugins_config_ns["copybutton_remove_prompts"]
copybutton_copy_empty_lines = plugins_config_ns["copybutton_copy_empty_lines"]
copybutton_selector = plugins_config_ns["copybutton_selector"]

# ---------------------------------- git_last_updated ---------------------------------
git_last_updated_timezone = plugins_config_ns["git_last_updated_timezone"]
git_last_updated_cache_path = plugins_config_ns["git_last_updated_cache_path"]
git_last_updated_metatags = plugins_config_ns["git_last_updated_metatags"]
git_untracked_show_sourcelink = plugins_config_ns["git_untracked_show_sourcelink"]
html_last_updated_fmt = plugins_config_ns["html_last_updated_fmt"]

# ---------------------------------- myst_parser ---------------------------------
myst_enable_extensions = plugins_config_ns["myst_enable_extensions"]
myst_number_code_blocks = plugins_config_ns["myst_number_code_blocks"]
myst_links_external_new_tab = plugins_config_ns["myst_links_external_new_tab"]
myst_heading_anchors = plugins_config_ns["myst_heading_anchors"]
myst_fence_as_directive = set(plugins_config_ns["myst_fence_as_directive"])
# 替换变量
myst_substitutions = {
    "version": release,
    "contact": "[支持邮箱](mailto:support@example.com)",
    "tody": TimeUtils.get_current_date(),
}
myst_substitutions.update(icons.ICONS_ELEMENT)

# ---------------------------------- notfound ---------------------------------
notfound_template = plugins_config_ns["notfound_template"]
notfound_context = plugins_config_ns["notfound_context"]
notfound_urls_prefix = plugins_config_ns["notfound_urls_prefix"]

# ---------------------------------- myst_nb ---------------------------------
nb_execution_mode = plugins_config_ns["nb_execution_mode"]
nb_execution_excludepatterns = plugins_config_ns["nb_execution_excludepatterns"]
nb_merge_streams = plugins_config_ns["nb_merge_streams"]
nb_execution_raise_on_error = plugins_config_ns["nb_execution_raise_on_error"]
nb_execution_timeout = plugins_config_ns["nb_execution_timeout"]
nb_execution_allow_errors = plugins_config_ns["nb_execution_allow_errors"]
nb_execution_in_temp = plugins_config_ns["nb_execution_in_temp"]
nb_execution_show_tb = plugins_config_ns["nb_execution_show_tb"]
nb_kernel_rgx_aliases = plugins_config_ns["nb_kernel_rgx_aliases"]
output_stderr = plugins_config_ns["output_stderr"]
nb_ipywidgets_js = plugins_config_ns["nb_ipywidgets_js"]

# ---------------------------------- mathjax ---------------------------------
mathjax_path = plugins_config_ns["mathjax_path"]
mathjax_config = plugins_config_ns["mathjax_config"]

#  ╔═══════════════════════════════════════════════════════════════════════════╗
#                                      生产环境
#  ╚═══════════════════════════════════════════════════════════════════════════╝


def setup(app: Sphinx):
    """
    Sphinx 标准扩展初始化
    :param app: Sphinx 应用实例
    :return:
    """
    is_print_context = False

    # noinspection PyUnusedLocal,PyShadowingNames
    def on_config_inited(app, config):
        """
        配置文件加载完成，但未解析任何文档，一般用于动态修改配置项、注册自定义扩展、验证配置有效性等
        :param app: Sphinx 实例
        :param config: 配置对象
        :return: None
        """
        ProductionConfigurator(app).apply()
        SEOManager(app).apply()

    # noinspection PyUnusedLocal,PyShadowingNames
    def on_builder_inited(app: Sphinx):
        """
        构建器初始化完成，但未加载任何文档，一般用于初始化全局配置/资源
        :param app: Sphinx 实例
        :return: None
        """
        env = app.builder.templates.environment
        config = app.config

        # logger.info(f"注册事件: {app.events.events.keys()}", 2)
        logger.info(f"当前主题: {config.html_theme}", 2)
        logger.info(f"已启用插件：{','.join(config.extensions)}", 2)
        JinjaFilters(app).add_filters()
        logger.info(f"已启用过滤器：{','.join(env.filters.keys())}", 2)
        # noinspection PyUnresolvedReferences,PyProtectedMember
        logger.info(f"已注册指令：{','.join(directives._directives.keys())}", 2)

        logger.info("[sphinx-extend] ⚡ 加载样式", 1)
        ThemeConfigManager().setup_sphinx_extension()

    # noinspection PyUnusedLocal,PyShadowingNames
    def on_env_before_read_docs(app: Sphinx, env, docnames):
        CoreManager(app).apply()

    # noinspection PyUnusedLocal,PyShadowingNames
    def on_doctree_resolved(app, doctree, docname):
        """
        文档已解析为 doctree 且所有转换完成，一般用于修改最终的文档树结构
        :param app: Sphinx 实例
        :param doctree: 文档树
        :param docname: 文档名称
        :return: None
        """
        CodeBlockManager(app).add_linenos(doctree)

    # noinspection PyUnusedLocal,PyShadowingNames
    def on_html_page_context(app: Sphinx, pagename, templatename, context, doctree):
        """
        生成 HTML页面前的上下文准备阶段，一般用于注入模板变量或动态内容
        :param app: Sphinx 实例
        :param pagename: 页面名称（通常为页面名称）
        :param templatename: 使用的模板名称
        :param context: 模板上下文
        :param doctree: 文档树
        :return: None
        """
        nonlocal is_print_context

        ContextManager(app).update_context(context)

        if not is_print_context:
            logger.info(
                f"页面'{pagename}'已注册模板变量：{','.join(context.keys())}", 2
            )
            is_print_context = True

        TranslatorManager(app).translate_ui(context)

    # noinspection PyUnusedLocal,PyShadowingNames
    def on_build_finished(app, exception):
        """
        构建完成的回调函数，一般用于清理临时资源、生成统计信息等
        :param app: Sphinx 实例
        :param exception: 构建过程中发生的异常（如果有）
        :return: None
        """
        if "index" in app.env.domaindata:
            logger.info(f"文档索引：\n{app.env.domaindata['index']}", 2)
        AssetManager(app).apply()

    # 按构建过程中事件触发顺序排序
    app.connect("config-inited", on_config_inited)
    app.connect("builder-inited", on_builder_inited)
    app.connect("env-before-read-docs", on_env_before_read_docs)
    logger.info("[sphinx-extend] ⚡ 优化代码块样式", 1)
    app.connect("doctree-resolved", on_doctree_resolved)
    logger.info("[sphinx-extend] ⚡ 注入模板变量（全局）", 1)
    # 调整优先级，确保在主题初始化后执行（sphinx_book_theme 主题默认是 501）
    app.connect("html-page-context", on_html_page_context, priority=502)
    app.connect("build-finished", on_build_finished)
